Cortex-M的开发环境有很多种,可以使用集成开发环境(IED),比如Keil,IAR等;也可使用GCC,使用GCC编译需要编写Makefile,比较麻烦,但是该方式能深入理解编译过程。另外ST开发了很多工具,也有自己的IDE,笔者不打算使用,但是笔者会使用STM32CubeMX软件开发STM32,这部分将在下一章讲解,本文将基于Keil和GCC开发,关于开发环境的搭建请参看上一章,笔者这里就不在赘述了。
笔者本文将以STM32为例,为了方便学习,笔者也会给出GD32和CH32的实例,请自行获取学习。
笔者使用的开发板是STM32F103ZET6。
4.1 Keil新建工程与配置
4.1.1文件准备
1.新建文件夹
新建STM32_Project目录,在STM32_Project文件夹下,我们新建7个文件夹,分别为CMSIS、FWLib、Listing、Output、Project、Readme、User。CMSIS 用来存放库为我们自带的启动文件和一些 Cortex-M系列的通用文件,CMSIS文件里存放的文件适合任何Cortex-M内核的单片机,CMSIS 的 缩写为:Cortex Microcontroller Software Interface Standard,是 ARM Cortex 微控制器软件接口标准,是 ARM 公司为芯片厂商提供的一套通用的且独立于芯片厂商 的处理器软件接口;FWlib 用来存放芯片厂家的库文件里面的 inc 和 src 这两个文件,这两个文件包含了芯片上的所有驱动。Listing 用来存放一些编译过程中产生的文件。Output用来保存软件编译后输出的文件。User用来存放工程文件和用户代码,包括主函数main.c。另外,再把文件名为keilkilll的文件放到STM32_Project文件夹用来清除不必要的文件。新建文件夹如下图所示:
2.添加文件
在STM32F10x_StdPeriph_Lib_V3.5.0目录下
STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver 的 inc 跟 src 这两个文件夹拷贝到 STM32_Project\FWlib 文件夹中。
STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm 的全部文件拷贝到 STM32_Project\CMSIS\startup(需先在 CMSIS 新建好 startup 文件夹)文件夹下。这些是用汇编写的启动文件。开发板用的 CPU 是 STM32F103ZE,V表示100脚,E = 512K字节的闪存存储器;其中 512KFlash,属于大容量的,所以等下我们把startup_stm32f10x_hd.s添加到我们的工程中。根据 ST 的官方资料:Flash 在 16 ~32 Kbytes 为小容量,64 ~128 Kbytes为 中容量,256 ~512 Kbytes为大容量,不同大小的 Flash 对应的启动文件不一 样,这点要注意。
STM32 各类产品对应的启动文件后缀名含义。
STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport 的 core_cm3.c 和 core_cm3.h 也拷贝到 STM32_Project\CMSIS 文件夹下。
STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x 的stm32f10x.h、system_stm32f10x.c、system_stm32f10x.h拷贝到STM32_Project\CMSIS 文件夹下。
STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template下的 main.c、stm32f10x_conf.h、 stm32f10x_it.h、 stm32f10x_it.c 、system_stm32f10x.c 拷贝到STM32_Project\User目录下,再在STM32_Project\User目录下新建YS_STM32,其中YS_STM32用来存放用户自定义文件。
4.1.2新建的MDK 工程
4.1.2.1新建工程
1.新建工程
启动Keil 5软件,在工具栏 Project->New μVision Project…新建我们的工程文件,我们将新建的工程文件保存在STM32_Project\Project文件夹下,文件名取为YS_STM32,名字可以随便取,点击保存。
2.选择芯片
新建工程后,会弹出窗口,这让我们选择片型号,我们用的芯片是 ST 公司的STM32F103ZE,有64K SRAM,512K Flash,属于高集成度的芯片。按如下选择即可。
接下来会弹出工程管理器,这里可以选择启动文件和CMSIS,这里就不选择了,我们这里用的是 ST 的库,库文件里面也自带了这一份启动代码,所以为了保持库的完整性,我们就不需要开发环境为我们自带的启动代码了,稍后我们自己手动添加。
3.工程管理
点击Keil的文件管理器,将 Target 改为 YS_STM32(不改也行),新建五个组,分别命名为STARTUP、FWlib、CMSIS、USER、Readme。STARTUP 从名字就可以看得出我们是用它来放我们的启动代码的,FWlib 用来存放库文件,CMSIS 用来存放 M系列单片机通用的文件,USER用来存放用户自定义的应用程序,Readme存放工程文档。然后添加相应文件。
修改main.c的代码。
/**
******************************************************************************
* @file main.c
* @author BruceOu
* @lib version V3.5.0
* @version V1.0
* @date 2021-02-06
* @blog https://blog.bruceou.cn/
* @Official Accounts 嵌入式实验楼
* @brief STM32F103ZET6工程模板
******************************************************************************
*/
/* Includes*********************************************************************/
#include "./LED/stm32f103_led.h"
/*延时函数*/
void Delay(u32 xms);
/**
* @brief 主函数
* @param None
* @retval int
*/
int main(void)
{
/* LED 初始化 */
LED_GPIO_Config();
while(1)
{
// ODR GPIOB
// 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 (复位值)
// 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
GPIOB->ODR = 0XFFFE; //低电平,GPIOB0(LED1)灯灭
Delay(0x4FFFFF);
GPIOB->ODR = 0XFFFF; //高电平,GPIOB0(LED1)灯亮
Delay(0x4FFFFF);
}
}
/**
* @brief 延时函数
* @param
xms 延时长度
* @retval None
*/
void Delay( u32 xms)
{
//for(; nCount != 0; nCount--);(方法一)
while(xms--);//(方法二)
}
/**********************************END OF FILE*******************************/
为了方便观察现象,这里写了个控制LED的实例,具体如何配置将在后文详细,当然啦,如果有一定基础,直接根据自己的板子修改GPIO的即可。
4.1.2.2配置 MDK 的配置选项
点击工具栏中的魔术棒按钮
在弹出来的窗口中选中Output点击 Select Folder for Objects… 设置编译后输出文件保存的位置(放在STM32_Project\Output文件夹下)。同时把 Create HEX File 和 Browse information 这两个选项框也选上。
同样在 Listing 这个选项卡中,我们也点击 Select Folder listings…定位到模板中的 Listing 文件夹。
在C/C++选项卡,在 Define 里面输入添加 USE_STDPERIPH_DRIVER,STM32F10X_HD。添加 USE_STDPERIPH_DRIVER 是为了屏蔽编译器的默认搜索路径,转而使用我们添加到工程中的 ST 的库,添加 STM32F10X_HD 是因为我们用的芯片是大容量的,添加了STM32F10X_HD 这个宏之后,库文件里面为大容量定义的寄存器我们就可以用了。芯片是小或中容量的时候宏要换成STM32F10X_LD 或者 STM32F10X_MD。其实不管是什么容量的,我们只要添加上STM32F10X_HD 这个宏即可,当你用小或者中容量的芯片时,那些为大容量定义的寄存器我不去访问就是了,反正也访问不了,关于这两个宏的详细解释参看后文的小贴士。在 Include Paths 栏点击,在这里添加库文件的搜索路径,这样就可以屏蔽掉默认的搜索路径。
4.1.2.3硬件调试配置
接着以上操作,这个工程默认的是软件仿真,如果开发板要用 J-LINK (ST-LINK同理)调试的话,还需要在 开发环境中做如下修改。实际上,我们开发程序的时候 80%都是在硬件上调试的。
然后设置下Port。
一般都是SW接口,到此,常用的配置就完成了。
最后再来编译下。
出现一下信息表示编译通过。
接下来就是可进行程序烧写和验证了。
当然啦,能下载的前提是需要安装J-link等设备,也是可以使用其他下载方式,后面的章节会详细描述。
4.2 GCC环境新建工程与配置
笔者已经在《STM32开发环境搭建》系列文章详细介绍了该方式,但是笔者使用STM32CubeMX生成的工程,Makefile也是STM32CubeMX生成的,笔者接下来要介绍如何在标准库的基础上使用GCC开发。
在Keil工程的基础上删除Listing、Output、Project目录,在顶层目录添加Makefile和链接文件STM32F103ZETx_FLASH.ld。
Makefile
##########################################################################################################################
# File automatically-generated by tool: [projectgenerator] version: [3.15.2] date: [Fri Apr 08 20:05:41 CST 2022]
##########################################################################################################################
# ------------------------------------------------
# Generic Makefile (based on gcc)
#
# ChangeLog :
# 2022-04-08 - first version
# ------------------------------------------------
######################################
# target
######################################
TARGET = YS_STM32
######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = -O0
#######################################
# paths
#######################################
# Build path
BUILD_DIR = build
######################################
# source
######################################
# C sources
C_SOURCES = \
CMSIS/system_stm32f10x.c \
User/main.c \
User/stm32f10x_it.c \
FWLib/src/stm32f10x_rcc.c \
FWLib/src/stm32f10x_gpio.c \
User/YS_STM32/LED/stm32f103_led.c
# ASM sources
ASM_SOURCES = \
CMSIS/startup/startup_stm32f10x_hd.s
#######################################
# binaries
#######################################
PREFIX = arm-none-eabi-
# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
#######################################
# CFLAGS
#######################################
# cpu
CPU = -mcpu=cortex-m3
# fpu
# NONE for Cortex-M0/M0+/M3
# float-abi
# mcu
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)
# macros for gcc
# AS defines
AS_DEFS =
# C defines
C_DEFS = \
-DUSE_STDPERIPH_DRIVER \
-DSTM32F10X_HD
# AS includes
AS_INCLUDES =
# C includes
C_INCLUDES = \
-ICMSIS \
-IFWLib/inc \
-IUser \
-IUser/YS_STM32
# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif
# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"
#######################################
# LDFLAGS
#######################################
# link script
LDSCRIPT = STM32F103ZETx_FLASH.ld
# libraries
LIBS = -lc -lm -lnosys
LIBDIR =
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections
# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@
$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
$(AS) -c $(CFLAGS) $< -o $@
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
$(SZ) $@
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(HEX) $< $@
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(BIN) $< $@
$(BUILD_DIR):
mkdir $@
#######################################
# clean up
#######################################
clean:
-rm -fR $(BUILD_DIR)
#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)
# *** EOF ***
STM32F103ZETx_FLASH.ld
/*
******************************************************************************
**
** File : LinkerScript.ld
**
**
** Abstract : Linker script for STM32F103ZETx series
** 512Kbytes FLASH and 64Kbytes RAM
**
** Set heap size, stack size and stack location according
** to application requirements.
**
** Set memory bank area and size if external memory is used.
**
** Target : STMicroelectronics STM32
**
** Distribution: The file is distributed “as is,” without any warranty
** of any kind.
**
*****************************************************************************
*/
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_estack = 0x20010000; /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200; /* required amount of heap */
_Min_Stack_Size = 0x400; /* required amount of stack */
/* Specify the memory areas */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 512K
}
/* Define output sections */
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
} >FLASH
/* Constant data goes into FLASH */
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
.ARM : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH
/* used by the startup to initialize data */
_sidata = LOADADDR(.data);
/* Initialized data sections goes into RAM, load LMA copy after code */
.data :
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM AT> FLASH
/* Uninitialized data section */
. = ALIGN(4);
.bss :
{
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >RAM
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM
/* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}
然后修改启动文件,将STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ ST\STM32F10x\startup\gcc_ride7 的全部文件拷贝到 STM32_Project\CMSIS\startup目录下,当前的启动文件就使用startup_stm32f10x_hd.s。
接下来就可以编译了。
关于GCC环境的搭建参看笔者前面的文章就可以了,Linux和Windows板本的都有。
笔者是在Windows开发,这里贴出GCC编译的环境搭建,为了方便查看。
【注】这里要选择GUN-RM下的工具,GUN-A是Cortex-A系列的交叉编译工具。
下载后解压,并把安装目录下的bin文件夹添加到环境变量:
然后在命令窗口中输入下面的命令验证安装是否成功:
arm-none-eabi-gcc -v
如果有信息输出,那就是装好了。
GCC编译工程是需要编写Makefile,这里笔者已经编写好了,直接用就行。那么就需要一个工具来识别Makefile文件,也就是make工具,在Linux中已经自带make了,这一步就可以省了。
本文的make工具是依赖Git工具的,我相信很多朋友都用过Git,但是很少使用Git的make等功能。
Git的bash实际上也就是一个mingw,是可以支持部分Linux指令的,但是只有少部分。在编译代码的时候经常会使用make命令反而在bash下默认是不支持的。
下载make-4.1-2-without-guile-w32-bin.zip 文件。
把该文件进行解压,把解压出来的文件全部拷贝的git的安装目录下:
C:\Program Files\Git\mingw64
把文件夹进行合并,如果跳出来需要替换的文件要选择不替换。
这样在git bash窗口下就可以执行make了。
没有安装Git先安装Git工具。
Git下载地址:https://git-scm.com/download/win
最后我们使用make编译下前文新建的工程,编译通过显示如下:
编译成功就会在build目录下生成固件。
值得注意的是,国产的很多厂家都没有提供GCC版本的启动文件,需要自行修改,笔者在后面会介绍如何编写启动文件。
在后面的章节笔者讲解程序的下载与调试,尽请期待!
小贴士
关于STM32 MDK中USE_STDPERIPH_DRIVER, STM32F10X_HD问题的解释
初学STM32,在MDK 环境中使用STM32固件库建立工程时,初学者可能会遇到编译不通过的问题。出现如下警告或错误提示:
warning: #223-D: function "assert_param" declared implicitly;assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
这时候我们需要在“Target Options”中的“C/C++”选项卡中如下图所示红框中添加USE_STDPERIPH_DRIVER、STM32F10X_HD。这样才能使编顺利通过。
知其然了,我们还得知其所以然。下面就笔者给大家一一道来。我们知道,程序的执行是从“main.c”文件开始的,其中必须包含有头文件“stm32f10x.h”。我们打开“stm32f10x.h”,按下“Ctrl+F”键,查找USE_STDPERIPH_DRIVER,在“Find What”栏中输入“USE_STDPERIPH_DRIVER”。值得注意的是在查找之前工程必须是编译过了的。如下图所示。
点击“Find Next”,出现“USE_STDPERIPH_DRIVER”对应的代码行,我们能在第8296-8298行找到如下图所示代码段。
这段代码的意思是,只有用预编译指令预定义了“USE_STDPERIPH_DRIVER”,才会将"stm32f10x_conf.h"包含进“stm32f10x.h”中,从而被"main.c"用到。这就解释了,为什么我们没有在“main.c”中包含"stm32f10x_conf.h",而在编译之后却被包含进了"main.c"中,出现如图-5所示的情况。"stm32f10x_conf.h"文件相当于一个开关文件,如果要用到STM32固件库驱动标准外设,则外设驱动头文件是必不可少的,如“stm32f10x_gpio.h”。在"stm32f10x_conf.h"中我们通过代码#include "stm32f10x_gpio.h"来实现这个操作。
说到这儿估计大家已经对“USE_STDPERIPH_DRIVER”的来龙去脉有个清晰的认识了吧?其实单从字面意思理解就是“使用标准外设驱动”,在C/C++预定义中加“USE_STDPERIPH_DRIVER”就是允许“使用标准外设驱动”了。至于加入的“STM32F10X_HD“同样能在文件”stm32f10x.h“通过如上所述的方法通过功能查找来进行解释,其实它也是对于对应硬件的某些定义起开关作用罢了。其实写在这里,他的名字叫define。也就跟你在工程里面写define XXX是一样的。只不过写在这里的话,是全局的而已。
欢迎访问我的网站
BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书
BruceOu的知乎
资源获取方式
1.关注公众号[嵌入式实验楼]
2.在公众号回复关键词[Cortex-M]获取资料提取码